use crate::char_seq::{CharSeq, Pos};
use std::{
    borrow::Cow,
    ffi::{c_int, c_uint},
    fmt::Display,
};

#[derive(Debug, PartialEq)]
pub struct TokenPos {
    pub token: Token,
    pub pos: Pos,
}

impl TokenPos {
    pub fn new(data: Token, pos: Pos) -> Self {
        Self { token: data, pos }
    }
}

#[derive(Debug, Clone, PartialEq)]
pub enum CompareOperator {
    Eq, // =
    Ne, // =
    Lt, // <
    Le, // <=
    Gt, // >
    Ge, // >=
}

impl Display for CompareOperator {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            CompareOperator::Eq => write!(f, "=="),
            CompareOperator::Ne => write!(f, "!="),
            CompareOperator::Lt => write!(f, "<"),
            CompareOperator::Le => write!(f, "<="),
            CompareOperator::Gt => write!(f, ">"),
            CompareOperator::Ge => write!(f, ">="),
        }
    }
}

#[derive(Debug, PartialEq, Clone, Copy)]
pub enum Arithmetic {
    Add, // +
    Sub, // -
    // NOTE: multiple in makec is not '*', but 'X'
    Mul, // X
    Div, // /
    Mod, // %
}

impl Display for Arithmetic {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Arithmetic::Add => write!(f, "+"),
            Arithmetic::Sub => write!(f, "-"),
            Arithmetic::Mul => write!(f, "*"),
            Arithmetic::Div => write!(f, "/"),
            Arithmetic::Mod => write!(f, "%"),
        }
    }
}

#[derive(Debug, PartialEq)]
pub enum Token {
    Error(String),

    Char(char),

    Int(isize),
    UInt(usize),
    CInt(c_int),
    CUInt(c_uint),
    I32(i32),
    U32(u32),
    I64(i64),
    U64(u64),

    Boolean(bool),
    String(Cow<'static, str>),
    Identifier(Cow<'static, str>),

    CommentLine(String),

    // Split different syntax
    Colon, // :

    // Name in namespace.
    DColon, // ::

    // Get inner field, variant or function.
    Dot, // .

    // De-reference pointer and marker for pointer type.
    Star, // *
    // Nullable pointer.
    Question, // ?

    Lpare,   // (
    Rpare,   // )
    Lsquace, // [
    Rsquace, // ]
    Lbrace,  // {
    Rbrace,  // }

    Cmp(CompareOperator),

    Child, // |

    Not,  // !
    And,  // &&
    Or,   // ||
    BShl, // <<
    BShr, // >>
    BAnd, // &
    BOr,  // |
    BXor, // ^

    Ampersand, // '&'

    // + - * / %
    Arithmetic(Arithmetic),
    // += -= *= /= %=
    SelfArithmetic(Arithmetic),

    // Keywords
    Func,
    Import,
    Pub,
    Let,
    Return,

    If,
    Elif,
    Else,
    Switch,
    While,
    For,
    Break,
    Continue,
    Null,

    Struct,
    Enum,
    Union,
    Const,
    Void,
    Cast,
    Sizeof,
}

fn token_from_name(name: String) -> Token {
    match name.as_str() {
        "func" => Token::Func,
        "import" => Token::Import,
        "let" => Token::Let,
        "return" => Token::Return,
        "if" => Token::If,
        "elif" => Token::Elif,
        "else" => Token::Else,
        "switch" => Token::Switch,
        "while" => Token::While,
        "for" => Token::For,
        "break" => Token::Break,
        "continue" => Token::Continue,
        "null" => Token::Null,
        "struct" => Token::Struct,
        "enum" => Token::Enum,
        "union" => Token::Union,
        "const" => Token::Const,
        "pub" => Token::Pub,
        "void" => Token::Void,
        "cast" => Token::Cast,
        "sizeof" => Token::Sizeof,
        "true" => Token::Boolean(true),
        "false" => Token::Boolean(false),
        "not" => Token::Not,
        "and" => Token::And,
        "or" => Token::Or,
        "bshl" => Token::BShl,
        "bshr" => Token::BShr,
        "band" => Token::BAnd,
        "bor" => Token::BOr,
        "bxor" => Token::BXor,
        _ => Token::Identifier(name.into()),
    }
}

pub struct TokenSeq<Iter>
where
    Iter: Iterator<Item = char>,
{
    iter: CharSeq<Iter>,
}

impl<Iter> Iterator for TokenSeq<Iter>
where
    Iter: Iterator<Item = char>,
{
    type Item = TokenPos;

    fn next(&mut self) -> Option<Self::Item> {
        self.next_token_pos()
    }
}

type CowStr = Cow<'static, str>;

impl<Iter> TokenSeq<Iter>
where
    Iter: Iterator<Item = char>,
{
    pub fn new(iter: Iter, file: CowStr) -> Self {
        Self {
            iter: CharSeq::new(iter, file),
        }
    }

    pub fn next_token_pos(&mut self) -> Option<TokenPos> {
        loop {
            let c = self.iter.next()?;
            let pos = self.iter.pos();

            let Some(token) = self.next_token(c) else {
                continue;
            };
            return Some(TokenPos::new(token, pos));
        }
    }

    fn next_token(&mut self, c: char) -> Option<Token> {
        let token = match c {
            '0'..='9' => self.take_number_token(c),

            '_' | 'a'..='z' | 'A'..='Z' => {
                let mut name = String::new();

                name.push(c);

                while let Some(c) = self
                    .iter
                    .next_if(|c| c.is_ascii_alphanumeric() || *c == '_')
                {
                    name.push(c);
                }

                if (name.starts_with('_') || name.ends_with('_')) && name != "_" {
                    return Some(
                        Token::Error(
                            format!("invalid identifier `{name}', identifier can't starts or ends with `_' unless is a single `_' (hole identifer)")
                        )
                    );
                }

                if name == "X" {
                    if self.iter.next_if(|c| *c == '=').is_some() {
                        Token::SelfArithmetic(Arithmetic::Mul)
                    } else {
                        Token::Arithmetic(Arithmetic::Mul)
                    }
                } else {
                    token_from_name(name)
                }
            }

            '"' => match self.on_quote() {
                Ok(value) => value,
                Err(value) => return value,
            },

            '\'' => {
                let c = match self.iter.next() {
                    Some('\\') => match self.iter.next() {
                        Some('t') => Token::Char('\t'),
                        Some('r') => Token::Char('\r'),
                        Some('n') => Token::Char('\n'),
                        Some('0') => Token::Char('\0'),
                        Some('\'') => Token::Char('\''),
                        Some(c) => {
                            return Some(Token::Error(format!("invalid escape char: \\{c}")))
                        }
                        None => {
                            return Some(Token::Error(
                                "need a charactor after single quote escape ('\\)".to_string(),
                            ))
                        }
                    },
                    Some(c) => Token::Char(c),
                    None => {
                        return Some(Token::Error(
                            "need a charactor after single quote (')".to_string(),
                        ))
                    }
                };

                if !matches!(self.iter.next(), Some('\'')) {
                    return Some(Token::Error(
                        "need a charactor after single quote (')".to_string(),
                    ));
                }

                c
            }

            // Comment
            '#' => {
                let mut line = String::new();

                while let Some(c) = self.iter.next_if(|c| *c != '\n') {
                    line.push(c);
                }

                // A comment line must be empty or starts with one space ' '
                if line.is_empty() {
                    Token::CommentLine(line)
                } else {
                    let Some(line) = line.strip_prefix(' ') else {
                        return Some(Token::Error(
                            "Comment line not starts with a space".to_string(),
                        ));
                    };

                    Token::CommentLine(line.to_owned())
                }
            }

            '(' => Token::Lpare,
            ')' => Token::Rpare,
            '[' => Token::Lsquace,
            ']' => Token::Rsquace,
            '{' => Token::Lbrace,
            '}' => Token::Rbrace,
            ':' => {
                if self.iter.next_if(|c| *c == ':').is_some() {
                    Token::DColon
                } else {
                    Token::Colon
                }
            }

            '+' => match self.iter.peek() {
                Some('=') => {
                    self.iter.next();
                    Token::SelfArithmetic(Arithmetic::Add)
                }
                _ => Token::Arithmetic(Arithmetic::Add),
            },
            '-' => match self.iter.peek() {
                Some('=') => {
                    self.iter.next();
                    Token::SelfArithmetic(Arithmetic::Sub)
                }
                Some('0'..='9') => self.take_number_token('-'),
                _ => Token::Arithmetic(Arithmetic::Sub),
            },

            '*' => Token::Star,
            '?' => Token::Question,

            '/' => match self.iter.peek() {
                Some('=') => {
                    self.iter.next();
                    Token::SelfArithmetic(Arithmetic::Div)
                }
                _ => Token::Arithmetic(Arithmetic::Div),
            },

            '%' => match self.iter.peek() {
                Some('=') => {
                    self.iter.next();
                    Token::SelfArithmetic(Arithmetic::Mod)
                }
                _ => Token::Arithmetic(Arithmetic::Mod),
            },

            '.' => Token::Dot,

            '&' => Token::Ampersand,

            '|' => Token::Child,

            '!' => {
                if self.iter.next_if(|c| *c == '=').is_some() {
                    Token::Cmp(CompareOperator::Ne)
                } else {
                    Token::Not
                }
            }

            '=' => Token::Cmp(CompareOperator::Eq),
            '<' => {
                if self.iter.next_if(|c| *c == '=').is_some() {
                    Token::Cmp(CompareOperator::Le)
                } else {
                    Token::Cmp(CompareOperator::Lt)
                }
            }
            '>' => {
                if self.iter.next_if(|c| *c == '=').is_some() {
                    Token::Cmp(CompareOperator::Ge)
                } else {
                    Token::Cmp(CompareOperator::Gt)
                }
            }

            ' ' | '\t' | '\r' | '\n' => return None,

            c => Token::Error(format!("unexpected charactor: {c:?}")),
        };

        Some(token)
    }

    fn take_number_token(&mut self, first_char: char) -> Token {
        let mut number = String::new();
        number.push(first_char);

        while let Some(c) = self.iter.next_if(|c| c.is_ascii_digit() || *c == '_') {
            if c != '_' {
                number.push(c);
            }
        }

        let mut suffix = String::new();
        while let Some(c) = self
            .iter
            .next_if(|c| c.is_ascii_alphanumeric() || *c == '_')
        {
            if c != '_' {
                suffix.push(c);
            }
        }

        match suffix.as_str() {
            "i" => Token::Int(number.parse().unwrap()),
            "u" | "" => Token::UInt(number.parse().unwrap()),
            "ci" => Token::CInt(number.parse().unwrap()),
            "cu" => Token::CUInt(number.parse().unwrap()),
            "i32" => Token::I32(number.parse().unwrap()),
            "u32" => Token::U32(number.parse().unwrap()),
            "i64" => Token::I64(number.parse().unwrap()),
            "u64" => Token::U64(number.parse().unwrap()),
            _ => Token::Error(format!("invalid suffix for number: {suffix}")),
        }
    }

    fn on_quote(&mut self) -> Result<Token, Option<Token>> {
        let mut text = String::new();
        let mut escape = false;

        loop {
            match (escape, self.iter.next()) {
                (_, None) => {
                    return Err(Some(Token::Error(
                        ("reach EOF in string literal").to_string(),
                    )))
                }
                (false, Some('\\')) => escape = true,
                (false, Some('"')) => break,
                (false, Some(c)) => text.push(c),
                (true, Some(c)) => {
                    text.push(match c {
                        'n' => '\n',
                        't' => '\t',
                        'r' => '\r',
                        _ => {
                            return Err(Some(Token::Error(format!(
                                "invalid escape charactor '\\{c}'",
                            ))))
                        }
                    });
                    escape = false;
                }
            }
        }
        Ok(Token::String(Cow::Owned(text)))
    }
}
